#include <cstdlib>
#include <vector>
#include "UTAXI4RAM.hpp"
#include "openvip/ovip.h"
#include "openvip_third/nutshell.h"

using namespace xspcomm;

XClock *pclk;
ovip::AXI4MasterPortAS *pram_bus;
UTAXI4RAM *pdut = nullptr;

inline ovip::AXIData rand_data() {
    ovip::AXIData data;
    for (int i = 0; i < 8; i++)
        data.push_back(rand() % 256);
    return data;
}
inline ovip::AXIAddr rand_addr() { return rand() % 512 * 8; }

ovip::AXIData ram[512];
void test_axi4ram() {
    /* Init DUT */

    char **argv = new char *[1];
    argv[0]     = new char[50];
    strcpy(argv[0], "dut/UT_nutshell_axi4ram_trace/libDPIAXI4RAM.so");
    UTAXI4RAM dut(1, argv);
    pdut = &dut;

    std::function<int(bool)> stepfunc = [&dut](bool d) {
        dut.step(d);
        return 0;
    };
    XClock clk(stepfunc, {&dut.clock}, {&dut.port});
    ovip::AXI4MasterPortAS ram_bus("ram_bus", dut.port, clk, "io_in_");

    pclk = &clk;
    pram_bus = &ram_bus;

    dut.port["reset"] = 1;
    clk.Step();
    dut.port["reset"] = 0;
    for (int i = 0; i < 10; i++)
        clk.Step();

    /* Test */

    Info("Test read/write");

    for (ovip::AXIAddr addr = 0; addr < 4096; addr += 8) {
        ovip::AXIData expect_data = ram[addr / 8] = rand_data();
        ram_bus.write(addr, expect_data, [](ovip::AXI4MasterPortAS *master, ovip::AXIResp resp, void *args) {
            assert(resp == ovip::OKAY);
        });
        ram_bus.read(addr, [expect_data](ovip::AXI4MasterPortAS *master, ovip::AXIData read_data, void *args) {
            assert(read_data == expect_data);
        });
    }
    while (ram_bus.is_busy()) clk.Step();

    Info("Test random read/write");

    for (int i = 0; i < 1024; i++) {
        ovip::AXIAddr addr = rand_addr();
        if (rand() % 2) {
            ovip::AXIData expect_data = ram[addr / 8] = rand_data();
            ram_bus.write(addr, expect_data, [](ovip::AXI4MasterPortAS *master, ovip::AXIResp resp, void *args) {
                Assert(resp == ovip::OKAY, "write resp need be OKAY");
            });
        } else {
            ovip::AXIData expect_data = ram[addr / 8];
            ram_bus.read(addr, [expect_data, i](ovip::AXI4MasterPortAS *master, ovip::AXIData read_data, void *args) {
                assert(read_data == expect_data);
            });
        }
    }
    while (ram_bus.is_busy()) clk.Step();

    Info("Test read burst");

    for (int i = 0; i < 1024; i++) {
        int len = rand() % 128 + 1;
        ovip::AXIAddr addr = rand() % (512 - len) * 8;
        ovip::AXIDataVec data;
        for (int j = 0; j < len; j++) {
            data.push_back(ram[addr / 8 + j]);
        }
        ram_bus.read_burst(addr, len, [data, len](ovip::AXI4MasterPortAS *master, ovip::AXIDataVec read_data, void *args) {
            for (int j = 0; j < len; j++)
                assert(read_data[j] == data[j]);
        });
    }
    while (ram_bus.is_busy()) clk.Step();


    Info("Test write burst");

    for (int i = 0; i < 1024; i++) {
        int len = rand() % 128 + 1;
        int addr = rand() % (512 - len) * 8;
        ovip::AXIDataVec data;
        for (int i = 0; i < len; i++) {
            ram[addr / 8 + i] = rand_data();
            data.push_back(ram[addr / 8 + i]);
        }
        ram_bus.write_burst(addr, data, [](ovip::AXI4MasterPortAS *master, ovip::AXIResp resp, void *args) {
            Assert(resp == ovip::OKAY, "write resp need be OKAY");
        });
    }
    while (ram_bus.is_busy()) clk.Step();

    for (ovip::AXIAddr addr = 0; addr < 4096; addr += 8) {
        ovip::AXIData expect_data = ram[addr / 8];
        ram_bus.read(addr, [expect_data](ovip::AXI4MasterPortAS *master, ovip::AXIData read_data, void *args) {
            assert(read_data == expect_data);
        });
    }
    while (ram_bus.is_busy()) clk.Step();

    for (int i = 0; i < 10; i++)
        pclk->Step();

    Info("TEST PASSED");
}


int main() {
    Info("Test axi4ram (axi4 async)...");

    signal(SIGINT, [](int sig) {
        pdut->finalize();
        Info("SIGINT(%d) received, exit!", sig);
        exit(0);
    });

    test_axi4ram();

    return 0;
}
